#include "PageRequestHandler.h"
#include "GlobalConfig.h"

#include "Poco/Net/HTTPServer.h"
#include "Poco/Net/HTTPRequestHandler.h"
#include "Poco/Net/HTTPRequestHandlerFactory.h"
#include "Poco/Net/HTTPServerParams.h"
#include "Poco/Net/HTTPServerRequest.h"
#include "Poco/Net/HTTPServerResponse.h"
#include "Poco/Net/HTTPServerParams.h"
#include "Poco/Net/ServerSocket.h"
#include "Poco/Net/WebSocket.h"
#include "Poco/Net/NetException.h"
#include "Poco/Util/ServerApplication.h"
#include "Poco/Util/Option.h"
#include "Poco/Util/OptionSet.h"
#include "Poco/Util/HelpFormatter.h"
#include "Poco/Format.h"
#include <iostream>


using Poco::Net::ServerSocket;
using Poco::Net::WebSocket;
using Poco::Net::WebSocketException;
using Poco::Net::HTTPRequestHandler;
using Poco::Net::HTTPRequestHandlerFactory;
using Poco::Net::HTTPServer;
using Poco::Net::HTTPServerRequest;
using Poco::Net::HTTPResponse;
using Poco::Net::HTTPServerResponse;
using Poco::Net::HTTPServerParams;
using Poco::Timestamp;
using Poco::ThreadPool;
using Poco::Util::ServerApplication;
using Poco::Util::Application;
using Poco::Util::Option;
using Poco::Util::OptionSet;
using Poco::Util::HelpFormatter;


#include <Poco/Net/HTTPServerResponse.h>

#include <sstream>
#include <regex>
#include <string>

#include "PingRequestHandler.h"

#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>
#include <boost/filesystem.hpp>
#include <vector>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/split.hpp>
#include <thread>

#include <Poco/String.h>

PageRequestHandler::PageRequestHandler()
{
}


PageRequestHandler::~PageRequestHandler()
{
}

// Trick to define a recursive function within this scope (for example purposes)
void PageRequestHandler::read_and_send(Poco::Net::HTTPServerResponse& response, const std::shared_ptr<std::ifstream> &ifs)
{
	std::stringstream ss;
	// Read and send 128 KB at a time
	static std::vector<char> buffer(131072); // Safe when server is running on one thread
											 //buffer.clear();
	std::streamsize read_length;
	if ((read_length = ifs->read(&buffer[0], static_cast<std::streamsize>(buffer.size())).gcount()) > 0)
	{
		ss << std::string(&buffer[0], read_length);
		response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
		response.sendBuffer(ss.str().c_str(), ss.str().length());
		//con->send_http_response();
		if (read_length == static_cast<std::streamsize>(buffer.size()))
		{
			read_and_send(response, ifs);
		}
	}
}

void PageRequestHandler::handleRequest(Poco::Net::HTTPServerRequest& request, Poco::Net::HTTPServerResponse& response)
{
	// print request
	Application& app = Application::instance();
	std::stringstream ss;
	ss << "getMethod: " << request.getMethod() << std::endl;
	ss << "getURI: " << request.getURI() << std::endl;
	ss << "clientAddress: " << request.clientAddress().toString() << std::endl;
	ss << "serverAddress: " << request.serverAddress().toString() << std::endl;
	ss << "serverParams: " << "getServerName:" << request.serverParams().getServerName() << std::endl;
	ss << "getSoftwareVersion: " << request.serverParams().getSoftwareVersion() << std::endl;
	ss << "getTimeout: " << request.serverParams().getTimeout().totalSeconds() << std::endl;
	ss << "getKeepAlive: " << request.serverParams().getKeepAlive() << std::endl;


	std::string body;
	std::ostringstream os;
	request.stream() >> os.rdbuf();
	body = os.str();
	ss << "stream: " << body << std::endl;
	app.logger().information(ss.str());

	auto uri = request.getURI();

	//std::transform(subp.begin(), subp.end(), subp.begin(), ::tolower);
	if (request.getMethod() == "POST")
	{
		std::stringstream ostr;
		if (std::regex_match(uri, std::regex("^/string$")))
		{
			std::string content = body;

			ostr << "HTTP/1.1 200 OK\r\nContent-Length: " << content.length() << "\r\n\r\n" << content;
		}
		else if (std::regex_match(uri, std::regex("^/json$")))
		{
			// parse "{\"firstName\": \"John\",\"lastName\": \"Smith\",\"age\": 25}"
			try
			{
				boost::property_tree::ptree pt;
				std::stringstream ss(body);
				boost::property_tree::json_parser::read_json(ss, pt);
				auto name = pt.get<std::string>("firstName") + " " + pt.get<std::string>("lastName");

				ostr << "HTTP/1.1 200 OK\r\n"
					<< "Content-Length: " << name.length() << "\r\n\r\n"
					<< name;
			}
			catch (std::exception& e)
			{
				ostr << "HTTP/1.1 400 Bad Request\r\nContent-Length: " << strlen(e.what()) << "\r\n\r\n" << e.what();
			}
		}

		response.setChunkedTransferEncoding(true);
		response.setContentType("text/html");
		response.sendBuffer(ostr.str().c_str(), ostr.str().length());
		return;
	}
	else if (request.getMethod() == "GET")
	{
		// GET-example for the path /info
		// Responds with request-information
		if (std::regex_match(uri, std::regex("^/info.*$")))
		{
			if (std::regex_match(uri, std::regex("^/info/json$")))
			{
				boost::property_tree::ptree pt;
				pt.put("clientAddress", request.clientAddress().toString());
				pt.put("serverAddress", request.serverAddress().toString());
				pt.put("uri", request.getURI());
				pt.put("methond", request.getMethod());
				std::ostringstream os;
				boost::property_tree::json_parser::write_json(os, pt);

				response.setChunkedTransferEncoding(true);
				response.setContentType("text/json");
				response.sendBuffer(os.str().c_str(), os.str().length());

				return;
			}

			std::stringstream res;
			res << "<h1>Request from " << request.clientAddress().toString() << "</h1>";

			res << request.getMethod() << " " << request.getURI();

			res << "<h2>Body</h2>";
			res << body;
			response.setChunkedTransferEncoding(true);
			response.setContentType("text/html");
			response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
			response.sendBuffer(os.str().c_str(), os.str().length());
		}
		// GET-example simulating heavy work in a separate thread
		else if (std::regex_match(uri, std::regex("^/work$")))
		{
			std::thread work_thread([&response] {
				std::stringstream res;
				std::this_thread::sleep_for(std::chrono::seconds(5));
				res << "Work done!";
				response.setChunkedTransferEncoding(true);
				response.setContentType("text/text");
				response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
				response.sendBuffer(res.str().c_str(), res.str().length());
			});
			work_thread.detach();
			return;
		}
		// GET-example for the path /match/[number], responds with the matched string in path (number)
		// For instance a request GET /match/123 will receive: 123
		else if (std::regex_match(uri, std::regex("^/match/([0-9]+)$")))
		{
			std::smatch sm;
			std::regex e("^/match/([0-9]+)$");
			std::regex_match(uri, sm, e);
			std::stringstream res;
			res << std::endl << "match:" << sm[1];
			response.setChunkedTransferEncoding(true);
			response.setContentType("text/text");
			response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
			response.sendBuffer(res.str().c_str(), res.str().length());
		}
		else if (std::regex_match(uri, std::regex("^/ws$")))
		{
			std::stringstream ostr;
			ostr << "<html>";
			ostr << "<head>";
			ostr << "<title>WebSocketServer</title>";
			ostr << "<script type=\"text/javascript\">";
			ostr << "function WebSocketTest()";
			ostr << "{";
			ostr << "  if (\"WebSocket\" in window)";
			ostr << "  {";
			ostr << "    var ws = new WebSocket(\"ws://127.0.0.1:9002\");";
			ostr << "    ws.onopen = function()";
			ostr << "      {";
			ostr << "        ws.send(\"Hello, world!\");";
			ostr << "      };";
			ostr << "    ws.onmessage = function(evt)";
			ostr << "      { ";
			ostr << "        var msg = evt.data;";
			ostr << "        alert(\"Message received: \" + msg);";
			ostr << "        ws.close();";
			ostr << "      };";
			ostr << "    ws.onclose = function()";
			ostr << "      { ";
			ostr << "        alert(\"WebSocket closed.\");";
			ostr << "      };";
			ostr << "  }";
			ostr << "  else";
			ostr << "  {";
			ostr << "     alert(\"This browser does not support WebSockets.\");";
			ostr << "  }";
			ostr << "}";
			ostr << "</script>";
			ostr << "</head>";
			ostr << "<body>";
			ostr << "  <h1>WebSocket Server</h1>";
			ostr << "  <p><a href=\"javascript:WebSocketTest()\">Run WebSocket Script</a></p>";
			ostr << "</body>";
			ostr << "</html>";

			response.setChunkedTransferEncoding(true);
			response.setContentType("text/html");
			response.setStatus(Poco::Net::HTTPResponse::HTTP_OK);
			response.sendBuffer(ostr.str().c_str(), ostr.str().length());
		}
		else if (std::regex_match(uri, std::regex("^/$")))
		{
			std::string rootFilePath = GlobalConfig::ServerConfig::instance().docRoot;
			if (!rootFilePath.empty())
				rootFilePath += "/index.html";
			else
			{
				auto web_root_path = boost::filesystem::canonical("web");
				auto path = boost::filesystem::canonical(web_root_path / request.getURI());
				// Check if path is within web_root_path
				if (std::distance(web_root_path.begin(), web_root_path.end()) > std::distance(path.begin(), path.end()) ||
					!std::equal(web_root_path.begin(), web_root_path.end(), path.begin()))
					throw std::invalid_argument("path must be within root path");
				if (boost::filesystem::is_directory(path))
					path /= "index.html";

				rootFilePath = path.string();
			}
			auto ifs = std::make_shared<std::ifstream>();
			ifs->open(rootFilePath, std::ifstream::in | std::ios::binary | std::ios::ate);

			if (*ifs)
			{
				auto length = ifs->tellg();
				ifs->seekg(0, std::ios::beg);
				response.setContentType("text/html");
				response.setContentLength(length);

				read_and_send(response, ifs);
				return;
			}
		}

		else
		{
			response.setStatusAndReason(Poco::Net::HTTPResponse::HTTP_NOT_FOUND);
			std::ostream &ostr = response.send();
			ostr << "HTTP ERROR 404.";
			ostr.flush();
		}
	}
}